home *** CD-ROM | disk | FTP | other *** search
/ Games of Daze / Infomagic - Games of Daze (Summer 1995) (Disc 1 of 2).iso / x2ftp / msdos / libs / anivga12 / compress.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1993-07-11  |  39.0 KB  |  1,147 lines

  1. {$UNDEF test}     {Wenn "test" definiert ist: Programm, sonst Unit}
  2. {$DEFINE RLE}     {Wenn "RLE"  definiert ist: Huffman _und_ RLE-Codierung}
  3. {$UNDEF IOcheck}  {Wenn "IOcheck" definiert ist: $I+, sonst $I-}
  4.  
  5. {$IFDEF test}
  6. {$A+,B-,D+,E+,F-,G-,I+,L+,N+,O-,R-,S+,V+,X-}
  7. {$M 32768,0,655360}
  8. {$ELSE}
  9. {$A+,B-,D+,E+,F-,G-,I+,L+,N+,O-,R-,S-,V-,X-}
  10. {$M 32768,0,655360}
  11. {$ENDIF}
  12.  
  13. {$IFDEF test}
  14. PROGRAM compression;
  15. {$ELSE}
  16. UNIT compression;
  17. INTERFACE
  18. {$ENDIF}
  19.  
  20. {Zweck    : Datenkompression nach Huffman (und RLE)}
  21. {Autor    : Kai Rohrbacher    }
  22. {Sprache  : TurboPascal 6.0   }
  23. {Datum    : 25.09.1992        }
  24. {Anmerkung: Die zur Verfügung gestellten "FileOfBytes" benötigen eine Menge}
  25. {           Speicher, so daß genügend Stackspeicher vorhanden sein muß;    }
  26. {           ebenso empfiehlt es sich, mit möglichst wenigen solcher Dateien}
  27. {           auszukommen, Stichwort: Mehrfachausnutzung! (Unschön, aber     }
  28. {           wirksam!)}
  29. {           ErrorCompress enthält den zuletzt aufgetretenen Fehler; diese  }
  30. {           Variable muß vom Anwender berücksichtigt und anschließend auf  }
  31. {           CompressErr_NoError zurückgesetzt werden!}
  32. USES CRT,DOS;
  33.  
  34. TYPE header=ARRAY[1..3] OF BYTE;                {Erkennungsheader für}
  35. CONST Kennung:header=(ORD('H'),ORD('U'),ORD('C')); {komprimierte Dateien}
  36. {$IFDEF RLE}
  37.       ESC:BYTE=$1B;
  38.       FFh:BYTE=$FF;
  39.       TemporaryFile='_RLE.$$$';
  40. {$ENDIF}
  41.  
  42. CONST BufSize=512; {E/A-Puffergröße = 512 Bytes}
  43.  
  44.       CompressErr_NoError=0;          {mögliche Fehlerkonstanten}
  45.       CompressErr_Size0  =1;
  46.       CompressErr_AlreadyCompressed=2;
  47.       CompressErr_FileNotFound=3;
  48.       CompressErr_FileNotOpen=4;
  49.       CompressErr_DiskFull=5;
  50.       CompressErr_Unknown=255;
  51.  
  52.       CompressError:BYTE=CompressErr_NoError;
  53.  
  54. TYPE Pbranch=^branch;
  55.      branch=RECORD
  56.              zeichen:BYTE;
  57.              links,rechts:Pbranch
  58.             END;
  59.      code=RECORD
  60.            bitcount:BYTE; {max. Astlänge (und damit auch Codelänge)=255 Bit!}
  61.            itself:ARRAY[0..31] OF BYTE {32 Byte=256 Bits für den Code selber}
  62.           END;
  63.  
  64.      Puffer=ARRAY[0..BufSize-1] OF BYTE;
  65.      {Folgender Typ wird nur für einen Typecast des E/A-Puffers gebraucht}
  66.      {und gehört eindeutig in die Rubrik "dirty tricks"...}
  67.      Kopf=RECORD 
  68.            info  :Header;
  69.            Laengeunkom,Laengekom:LONGINT;
  70.            fillup:ARRAY[SizeOf(Header)+SizeOf(LongInt)+SizeOf(LongInt)
  71.                         ..BufSize-1] OF BYTE
  72.           END;
  73.  
  74.      FileOfByte=RECORD
  75.                  datei:FILE;
  76.                  lesen,               {lesen oder schreiben?}
  77.                  komprimiert:BOOLEAN; {komprimiert oder normales File?}
  78.                  lenunkom,            {unkomprimierte Bytesanzahl}
  79.                  lenbitskom:LONGINT;  {Dateilänge in Bits}
  80.                  filebitpos:LONGINT;  {akt. BIT-Pos. im File}
  81.                  position:LONGINT;    {aktuelle Position im File}
  82.                  msdosSize:LONGINT;   {totale Länge des Files}
  83.                  bitzaehl:BYTE;       {Bitzähler für Bits in eabyte}
  84.                  buf:Puffer;          {Puffer für E/A-Operationen}
  85.                  bufIndex:WORD;       {Indexzeiger in buf}
  86.                  bufMax:WORD;         {-1=max. Wert von bufIndex}
  87.                  stamm:branch;        {Wurzel des Codebaums}
  88.                  codes:ARRAY[0..255] OF code;  {Codes selber}
  89.                  {$IFDEF RLE}
  90.                  RLEcount:INTEGER;
  91.                  RLEchar :BYTE;
  92.                  {$ENDIF}
  93.                 END;
  94.  
  95. {$IFNDEF test}
  96.  PROCEDURE WriteBits(VAR f:FileOfByte; wert,Stellen:BYTE);
  97.  PROCEDURE ReadBits(VAR f:FileOfByte; VAR wert:BYTE; Stellen:BYTE);
  98.  PROCEDURE _Assign(VAR f:FileOfByte; s:STRING);
  99.  PROCEDURE _Reset(VAR f:FileOfByte);
  100.  PROCEDURE _Rewrite(VAR f:FileOfByte);
  101.  PROCEDURE _Flush(VAR f:FileOfByte);
  102.  FUNCTION  _FilePos(VAR f:FileOfByte):LONGINT;
  103.  PROCEDURE _Close(VAR f:FileOfByte);
  104.  FUNCTION  _logicalEOF(VAR f:FileOfByte):BOOLEAN;
  105.  FUNCTION  _physicalEOF(VAR f:FileOfByte):BOOLEAN;
  106.  PROCEDURE Resync(VAR f:FileOfByte);
  107.  FUNCTION  _FileSize(VAR f:FileOfByte):LONGINT;
  108.  PROCEDURE _Write(VAR f:FileOfByte; VAR b:BYTE);
  109.  PROCEDURE _Read(VAR f:FileOfByte; VAR b:BYTE);
  110.  PROCEDURE _ReadByte(VAR f:FileOfByte; VAR b:BYTE);
  111.  {$IFDEF RLE}
  112.  PROCEDURE RLEcompress(name1,name2:PathStr; VAR fin,fout:FileOfByte; chatty:BOOLEAN);
  113.  {$ENDIF}
  114.  PROCEDURE _BlockRead(VAR f:FileOfByte; var buf; count: Word);
  115.  PROCEDURE __BlockRead(VAR f:FileOfByte; var buf; count: Word; VAR result:WORD);
  116.  PROCEDURE _BlockWrite(VAR f:FileOfByte; var buf; count: Word);
  117.  PROCEDURE __BlockWrite(VAR f:FileOfByte; var buf; count: Word; VAR result:WORD);
  118.  PROCEDURE compress(name1,name2:PathStr; chatty:BOOLEAN);
  119.  PROCEDURE decompress(name1,name2:PathStr; chatty:BOOLEAN);
  120.  
  121.  IMPLEMENTATION
  122. {$ENDIF}
  123.  
  124. CONST ANDMask:ARRAY[0..7] OF BYTE=(254,253,251,247,239,223,191,127);
  125.       OrMask :ARRAY[0..7] OF BYTE=(1,2,4,8,16,32,64,128);
  126.       LowerBits:ARRAY[1..8] OF BYTE=(1,3,7,15,31,63,127,255);
  127. VAR temp:FileOfByte;
  128.     {$IFDEF test}
  129.     vorher,nachher,gesamt:LONGINT; 
  130.     ch:CHAR;
  131.     datei1,datei2:PathStr;
  132.     s:STRING;
  133.     {$ENDIF}
  134.  
  135.  
  136. PROCEDURE WriteBits(VAR f:FileOfByte; wert,Stellen:BYTE);
  137. {rem: Schreibt das Byte "wert" in die Datei f und benutzt dazu "Stellen" Bits}
  138. VAR bits:BYTE;
  139.     i,ReallyWritten:WORD;
  140. BEGIN
  141.  IF f.lesen THEN exit;  {nur Ausgabedateien, bitte!}
  142.  inc(f.lenbitskom,Stellen);
  143.  IF f.bitzaehl>=Stellen
  144.   THEN BEGIN {genug Platz in aktuellem Byte f.buf[f.bufIndex]}
  145.         f.buf[f.bufIndex]:=(f.buf[f.bufIndex] SHL Stellen) OR wert;
  146.         dec(f.bitzaehl,stellen);
  147.         IF f.bitzaehl=0
  148.      THEN BEGIN {Byte fertig, ablegen und evtl. Puffer schreiben}
  149.                inc(f.bufIndex); f.bitzaehl:=8;
  150.                IF f.bufindex>bufSize-1
  151.         THEN BEGIN
  152.                       {$I-}
  153.                       BlockWrite(f.datei,f.buf,BufSize,ReallyWritten);
  154.                       {$IFDEF IOcheck} {$I+} {$ENDIF}
  155.                       f.bufIndex:=0;
  156.                       IF IOresult=103
  157.                        THEN BEGIN
  158.                              CompressError:=CompressErr_FileNotOpen;
  159.                              exit
  160.                             END
  161.                       ELSE IF ReallyWritten<>BufSize
  162.                        THEN BEGIN
  163.                              CompressError:=CompressErr_DiskFull;
  164.                              exit
  165.                             END
  166.                       ELSE IF IOresult<>0
  167.                        THEN BEGIN
  168.                              CompressError:=CompressErr_Unknown;
  169.                              exit
  170.                             END;
  171.                      END;
  172.               END;
  173.        END
  174.   ELSE BEGIN {Überhang ins nächste Byte!}
  175.         bits:=Stellen-f.bitzaehl; {Überhang ins nächste Byte}
  176.         f.buf[f.bufIndex]:=(f.buf[f.bufIndex] SHL f.bitzaehl) OR (wert SHR bits);
  177.         inc(f.bufIndex);
  178.         IF f.bufindex>bufSize-1
  179.      THEN BEGIN
  180.                {$I-}
  181.                BlockWrite(f.datei,f.buf,BufSize,ReallyWritten);
  182.                {$IFDEF IOcheck} {$I+} {$ENDIF}
  183.                f.bufIndex:=0;
  184.                IF IOresult=103
  185.                 THEN BEGIN
  186.                       CompressError:=CompressErr_FileNotOpen;
  187.                       exit
  188.                      END
  189.                ELSE IF ReallyWritten<>BufSize
  190.                 THEN BEGIN
  191.                       CompressError:=CompressErr_DiskFull;
  192.                       exit
  193.                      END
  194.                ELSE IF IOresult<>0
  195.                 THEN BEGIN
  196.                       CompressError:=CompressErr_Unknown;
  197.                       exit
  198.                      END;
  199.               END;
  200.         f.buf[f.bufIndex]:=wert; f.bitzaehl:=8-bits; {Überhang übernehmen}
  201.        END;
  202. END;
  203.  
  204. PROCEDURE ReadBits(VAR f:FileOfByte; VAR wert:BYTE; Stellen:BYTE);
  205. {rem: Liest "Stellen" Bits aus der Datei f und legt diesen Wert in "wert" ab}
  206. {     Dies ist ein rein *physikalisches* Lesen, es werden keine Uminterpre- }
  207. {     tierungen der daten vorgenommen!}
  208. VAR bits,temp:BYTE;
  209. BEGIN
  210.  inc(f.filebitpos,Stellen);
  211.  IF NOT f.lesen THEN exit;  {nur Eingabedateien, bitte!}
  212.  IF f.bitzaehl>=Stellen
  213.   THEN BEGIN {genug Daten in aktuellem Eingabebyte}
  214.         wert:=(f.buf[f.bufIndex] SHR (f.bitzaehl-Stellen))
  215.                AND LowerBits[Stellen];
  216.         dec(f.bitzaehl,Stellen);
  217.         IF f.bitzaehl=0
  218.      THEN BEGIN
  219.                f.bitzaehl:=8;
  220.                inc(f.bufindex);
  221.                IF f.bufIndex=SizeOf(f.buf)
  222.         THEN BEGIN {nächsten Block lesen}
  223.                       IF NOT EOF(f.datei)
  224.                        THEN BEGIN
  225.                              BlockRead(f.datei,f.buf,SizeOf(f.buf),f.bufMax);
  226.                              f.bufIndex:=0
  227.                             END
  228.                       {ELSE f.bufIndex:=512} {..um EOF mitzuteilen!}
  229.                      END;
  230.               END
  231.        END
  232.   ELSE BEGIN {Daten auch aus nächstem Byte benötigt}
  233.         bits:=Stellen-f.bitzaehl; {Überhang aus nächstem Byte}
  234.         temp:=f.buf[f.bufIndex] SHL bits; {Teil aus altem Byte}
  235.         inc(f.bufindex);
  236.         IF f.bufIndex=SizeOf(f.buf)
  237.      THEN BEGIN {nächsten Block lesen}
  238.                IF NOT EOF(f.datei)
  239.                 THEN BlockRead(f.datei,f.buf,SizeOf(f.buf),f.bufMax);
  240.                f.bufIndex:=0
  241.               END;
  242.         f.bitzaehl:=8-bits;
  243.         wert:=(temp OR (f.buf[f.bufIndex] SHR f.bitzaehl))
  244.                AND LowerBits[Stellen]
  245.        END;
  246. END;
  247.  
  248. PROCEDURE _Assign(VAR f:FileOfByte; s:STRING);
  249. BEGIN
  250.  assign(f.datei,s)
  251. END;
  252.  
  253. PROCEDURE ReadHeader(VAR f:FileOfByte);
  254. {rem: Liest aus der bereits zum lesen geöffneten Datei einen evtl. Header aus}
  255. {     (und erstellt für komprimierte Dateien den zugehörigen Codebaum)}
  256. LABEL break;
  257. VAR i,wert:BYTE;
  258.     help,dummyx:Pbranch;
  259. BEGIN
  260.  FOR i:=0 TO SizeOf(Header)-1 DO f.buf[i]:=0; {evtl. alte Infos löschen}
  261.  IF NOT EOF(f.datei)
  262.   THEN BlockRead(f.datei,f.buf,SizeOf(f.buf),f.bufMax)  {1.Block lesen}
  263.   ELSE f.bufMax:=0;   {signalisiere: keine Daten da!}
  264.  IF f.bufMax<SizeOf(Header)+SizeOf(LongInt)+SizeOf(LongInt)
  265.   THEN BEGIN {nichtkomprimierte "normale" Datei}
  266.         f.komprimiert:=FALSE;
  267.         f.lenunkom:=FileSize(f.datei); f.lenbitskom:=f.lenunkom SHL 3;
  268.         f.bufIndex:=0;
  269.        END
  270.   ELSE BEGIN {genauer prüfen, ob komprimiert oder nicht}
  271.         f.komprimiert:=TRUE;
  272.         FOR i:=1 TO SizeOf(Header) DO {Header auslesen:}
  273.          f.komprimiert:=f.komprimiert AND (Kopf(f.buf).info[i]=Kennung[i]);
  274.         IF f.komprimiert
  275.      THEN BEGIN {komprimierte Datei, Header überspringen}
  276.                f.lenunkom:=Kopf(f.buf).Laengeunkom;
  277.                f.lenbitskom:=Kopf(f.buf).Laengekom;
  278.                f.bufIndex:=SizeOf(Header)+SizeOf(LongInt)+SizeOf(LongInt);
  279.               END
  280.      ELSE BEGIN {normale Datei, bei Position 0 anfangen}
  281.                f.lenunkom:=FileSize(f.datei); f.lenbitskom:=f.lenunkom SHL 3;
  282.                f.bufIndex:=0;
  283.               END;
  284.        END;
  285.  f.bitzaehl:=8; {alle 8 Bits des 0.ten Datenbytes noch auslesen}
  286.  f.position:=0; {"0.tes" Datenbyte}
  287.  f.filebitpos:=f.bufIndex SHL 3; {Anzahl gelesene Bits}
  288.  {$IFDEF RLE}
  289.  f.RLEcount:=0; {noch kein RLE aktiv}
  290.  {$ENDIF}
  291.  
  292.  IF f.komprimiert
  293.   THEN BEGIN
  294.         f.stamm.links:=NIL;
  295.         f.stamm.rechts:=NIL;
  296.         help:=@f.stamm;
  297.         FOR i:=0 TO 255 DO
  298.          BEGIN
  299.           ReadBits(f,wert,2);
  300.           WHILE wert<2 DO  {Wert 2=EndOfOneCode, 3=EndOfAllCodes?}
  301.            BEGIN {Wert 0 oder 1 = normalen Wert gelesen}
  302.             IF wert=0
  303.              THEN BEGIN
  304.                    IF help^.links=NIL
  305.                     THEN BEGIN
  306.                           new(dummyx);
  307.                           help^.links:=dummyx;
  308.                           dummyx^.links:=NIL;
  309.                           dummyx^.rechts:=NIL;
  310.                          END;
  311.                    help:=help^.links;
  312.                   END
  313.              ELSE BEGIN
  314.                    IF help^.rechts=NIL
  315.                     THEN BEGIN
  316.                           new(dummyx);
  317.                           help^.rechts:=dummyx;
  318.                           dummyx^.links:=NIL;
  319.                           dummyx^.rechts:=NIL;
  320.                          END;
  321.                    help:=help^.rechts;
  322.                   END;
  323.             ReadBits(f,wert,2)
  324.            END;
  325.  
  326.  
  327.           IF wert=3 THEN goto break; {Wert 3=EndOfAllCodes}
  328.           help^.zeichen:=i;
  329.           help:=@f.stamm;
  330.          END; {of FOR}
  331.         break:;
  332.        END;
  333. END;
  334.  
  335. PROCEDURE _Reset(VAR f:FileOfByte);
  336. BEGIN
  337.  {$I-}
  338.  Reset(f.datei,1);
  339.  {$IFDEF IOcheck} {$I+} {$ENDIF}
  340.  IF IOresult<>0
  341.   THEN BEGIN
  342.         CompressError:=CompressErr_FileNotFound;
  343.         exit
  344.        END;
  345.  f.lesen:=TRUE;
  346.  f.msdosSize:=FileSize(f.datei);
  347.  ReadHeader(f)
  348. END;
  349.  
  350. PROCEDURE _Rewrite(VAR f:FileOfByte);
  351. {Geschrieben wird nur nichtkomprimiert, deshalb nichts weiter als Ausgabe}
  352. {auf Bits vorbereiten und für bessere Performance "blocken"}
  353. BEGIN
  354.  {$I-}
  355.  Rewrite(f.datei,1);
  356.  {$IFDEF IOcheck} {$I+} {$ENDIF}
  357.  IF IOresult=2
  358.   THEN BEGIN
  359.         CompressError:=CompressErr_FileNotFound;
  360.         exit
  361.        END
  362.  ELSE IF IOresult<>0
  363.   THEN BEGIN
  364.         CompressError:=CompressErr_Unknown;
  365.         exit
  366.        END;
  367.  f.lesen:=FALSE;
  368.  f.lenunkom:=0; {noch nix geschrieben}
  369.  f.lenbitskom:=0;
  370.  f.komprimiert:=FALSE;
  371.  f.BufIndex:=0;
  372.  f.position:=0;
  373.  f.filebitpos:=0;
  374.  f.bitzaehl:=8;  {#freie Bits in eabyte}
  375.  f.msdosSize:=0; {damit liefert _physicalEOF() immer TRUE}
  376. END;
  377.  
  378. PROCEDURE _Flush(VAR f:FileOfByte);
  379. {rem: Achtung! Ein _Flush() schreibt den Datenpuffer auf Disk, füllt aber das}
  380. {     letzte Byte auf, d.h.: _Flush() sollte nur zum Schluß (vor einem       }
  381. {     _Close() ) aufgerufen werden (was eh automatisch geschieht), oder wenn }
  382. {     man beim späteren einlesen das Auffüllen berücksichtigt!}
  383. VAR ReallyWritten:WORD;
  384. BEGIN
  385.  IF f.lesen THEN exit; {nur zum Schreiben geöffnete Dateien flushen!}
  386.  IF f.bitzaehl<>8
  387.   THEN BEGIN
  388.         WriteBits(f,0,f.bitzaehl); {letztes Byte auffüllen}
  389.         inc(f.position)            {zählt als ganzes Byte }
  390.        END;
  391.  {$I-}
  392.  BlockWrite(f.datei,f.buf,f.bufIndex,ReallyWritten);
  393.  {$IFDEF IOcheck} {$I+} {$ENDIF}
  394.  f.bitzaehl:=8;
  395.  IF IOresult=103
  396.   THEN BEGIN
  397.         CompressError:=CompressErr_FileNotOpen;
  398.         f.bufIndex:=0;
  399.         exit
  400.        END
  401.  ELSE IF ReallyWritten<>f.bufIndex
  402.   THEN BEGIN
  403.         CompressError:=CompressErr_DiskFull;
  404.         f.bufIndex:=0;
  405.         exit
  406.        END
  407.  ELSE IF IOresult<>0
  408.   THEN BEGIN
  409.         CompressError:=CompressErr_Unknown;
  410.         f.bufIndex:=0;
  411.         exit
  412.        END;
  413. END;
  414.  
  415. FUNCTION _FilePos(VAR f:FileOfByte):LONGINT;
  416. {rem: Geht nur dann, wenn per _Reset() geöffnete Dateien nur per _Read()  }
  417. {     (anstatt mit ReadBits()) gelesen werden, bzw. bei per _Rewrite() ge-}
  418. {     öffneten Dateien nur, wenn die Daten per _Write() statt WriteBits() }
  419. {     geschrieben werden!}
  420. {     Ein normales Dos.FilePos() geht schief, da Dateien _geblockt_ werden!}
  421. BEGIN
  422.  _FilePos:=f.position
  423. END;
  424.  
  425. PROCEDURE DelBaum(gabel:branch);
  426. {rem: Gibt den durch den Codebaum belegten Speicher wieder frei}
  427. BEGIN
  428.  IF (gabel.links<>NIL) OR (gabel.rechts<>NIL)
  429.   THEN BEGIN
  430.         DelBaum(gabel.links^);  dispose(gabel.links);
  431.         DelBaum(gabel.rechts^); dispose(gabel.rechts);
  432.        END;
  433. END;
  434.  
  435. PROCEDURE _Close(VAR f:FileOfByte);
  436. BEGIN
  437.  IF NOT f.lesen THEN _Flush(f);
  438.  IF CompressError<>CompressErr_NoError THEN exit;
  439.  {$I-}
  440.  Close(f.datei);
  441.  {$IFDEF IOcheck} {$I+} {$ENDIF}
  442.  IF IOresult=103
  443.   THEN BEGIN
  444.         CompressError:=CompressErr_FileNotOpen;
  445.         exit
  446.        END
  447.  ELSE IF IOresult<>0
  448.   THEN BEGIN
  449.         CompressError:=CompressErr_Unknown;
  450.         exit
  451.        END;
  452.  IF f.komprimiert THEN DelBaum(f.stamm);
  453. END;
  454.  
  455. {Zum Unterschied _logicalEOF() und _physicalEOF(): Solange eine MSDos-Datei  }
  456. {nur eine Datei enthält, sind die beiden Funktionen äquivalent; steht dagegen}
  457. {in _einem_ File noch zusätzliche Daten (bspw. 2 komprimierte Dateien nach-  }
  458. {einander, so würde _logicalEOF() nach dem Ende der 1.Datei bereits TRUE zu- }
  459. {rückliefern, hier braucht man dann _physicalEOF(), z.B. in der Art:         }
  460. { while not _physicalEOF(f) do   ;solange Datei nicht restlos leergemacht... }
  461. {  BEGIN   ;...lese eine komprimierte Datei aus dem File aus:}
  462. {   while not _logicalEOF(f) do  ;diese Schleife liest diese 1 Datei aus}
  463. {    BEGIN                  }
  464. {     _Read(f,b); [...]     }
  465. {    END                    }
  466. {   IF not _physicalEOF(f)  ;resynchronisieren, d.h.:}
  467. {    THEN Resync(f)         ;nächsten Header aus MSDos-File holen}
  468. {  END                      ;und nächste Datei aus File auslesen }
  469.  
  470. FUNCTION _logicalEOF(VAR f:FileOfByte):BOOLEAN;
  471. {rem: Prüft, ob logisches Ende der (komprimierten) Datei erreicht wurde.}
  472. {     Enthält die Datei *mehrere* Dateien, so muß _physicalEOF() mit    }
  473. {     herangezogen werden!}
  474. VAR laenge:LONGINT;
  475. BEGIN
  476.  IF NOT f.lesen
  477.   THEN _logicalEOF:=FilePos(f.datei)+1=FileSize(f.datei)
  478.   ELSE BEGIN
  479.         IF f.komprimiert
  480.          THEN _logicalEOF:=(f.filebitpos+1>f.lenbitskom)
  481.           {$IFDEF RLE}   AND (f.RLEcount=0)    {$ENDIF}
  482.          ELSE _logicalEOF:=f.filebitpos+1>f.lenunkom SHL 3
  483.        END;
  484.  {Datei ist zuende, wenn...}
  485.  { wir im letzten Byte sind und das letzte benutzte Bit erreicht wurde}
  486.  { (bei nichtkomprimierten Dateien) das letzte Byte gelesen wurde}
  487. END;
  488.  
  489.  
  490. FUNCTION _physicalEOF(VAR f:FileOfByte):BOOLEAN;
  491. {rem: Prüft, ob Datei *physikalisch* zuende ist; eine einfache Prüfung der }
  492. {     Art "eof(f.datei)" ginge schief, da ja _blockweise_ gelesen wird!    }
  493. {     Deshalb: Datei ist zuende, wenn der letzte Block gelesen wurde, die  }
  494. {     Datei _logisch_ zuende ist und keine (Winzdatei) mehr im Puffer steht}
  495. {     _physicalEOF:=EOF(f.datei) AND _logicalEOF(f) AND                    }
  496. {     ( (f.bufIndex+1>=f.bufMax) OR (f.bufIndex=0) )    sollte auch gehen  }
  497. VAR n:LONGINT;
  498. BEGIN
  499.  n:=FilePos(f.datei)-f.bufMax+f.BufIndex+1;
  500.  IF f.komprimiert
  501.   THEN _physicalEOF:=(n>=f.msdosSize) AND _logicalEOF(f)
  502.   ELSE _physicalEOF:=(n>f.msdosSize)
  503. END;
  504.  
  505. PROCEDURE Resync(VAR f:FileOfByte);
  506. {rem: Die Routine dient dazu, nach dem Ende einer Datei (=Datensatzes) in }
  507. {     einer Datei (=MSDos-File) die nächste Datei zum lesen vorzubereiten }
  508. {     und entspricht einem _Reset(), mit dem Unterschied, daß eben _nicht_}
  509. {     von dem MSDos-Anfang der Datei gelesen wird, sondern vom aktuellen  }
  510. {     FilePos-Zeiger der offenen Datei}
  511. VAR n:LONGINT;
  512. BEGIN
  513.  n:=FilePos(f.datei)-BufSize+f.bufIndex; {abs. Fileposition des akt. Bytes}
  514.  IF f.bitzaehl<>8 THEN inc(n);
  515.  Seek(f.datei,n);
  516.  IF f.komprimiert THEN DelBaum(f.stamm); {alten Codebaum löschen}
  517.  ReadHeader(f)
  518. END;
  519.  
  520. FUNCTION _FileSize(VAR f:FileOfByte):LONGINT;
  521. {rem: Achtung! Dies liefert nur die Größe der aktuellen _logischen_ Datei;}
  522. {     enthält eine _physikalische_ Datei mehrere logische Dateien, so gibt}
  523. {     es keine Möglichkeit, die Gesamtgröße herauszubekommen!!!           }
  524. {     N.B.: Dos.FileSize() hilft auch nicht, da dies ja die Größe der     }
  525. {     *komprimierten* Datei zurückliefert!!}
  526. BEGIN
  527.  _FileSize:=f.lenunkom
  528. END;
  529.  
  530. PROCEDURE _Write(VAR f:FileOfByte; VAR b:BYTE);
  531. BEGIN
  532.  WriteBits(f,b,8);
  533.  inc(f.position)
  534. END;
  535.  
  536. PROCEDURE _ReadByte(VAR f:FileOfByte; VAR b:BYTE);
  537. {rem: Liest ein _logisches_ Byte aus der Datei f und legt es in "b" ab; dabei}
  538. {     wird eine evtl. RLE-Komprimierung noch *nicht* berücksichtigt (Huffman-}
  539. {     codierung aber schon)!}
  540. VAR help:Pbranch;
  541.     wert:BYTE;
  542. BEGIN
  543.  IF f.komprimiert
  544.   THEN BEGIN
  545.         help:=@f.stamm;
  546.         REPEAT
  547.          ReadBits(f,wert,1);
  548.          IF wert=0 THEN help:=help^.links
  549.                    ELSE help:=help^.rechts;
  550.         UNTIL (help^.links=NIL) AND (help^.rechts=NIL);
  551.         b:=help^.zeichen
  552.        END
  553.   ELSE ReadBits(f,b,8);
  554.  inc(f.position);
  555. END;
  556.  
  557. {$IFDEF RLE}
  558. PROCEDURE RLEcompress(name1,name2:PathStr; VAR fin,fout:FileOfByte;
  559.                       chatty:BOOLEAN);
  560. { in: name1 = Name der zu komprimierenden Datei}
  561. {     name2 = Name der zu erzeugenden Datei}
  562. {     chatty= TRUE für: Programm ist etwas redseliger während der Arbeit}
  563. {out: name2 = Dateiinhalt von name1, aber komprimiert}
  564. {     CompressError = CompressErr_Size0, falls Datei name1 Länge 0 hat}
  565. {rem: Komprimiert die Datei name1 und legt das Ergebnis in Datei name2 ab.}
  566. {     Die Datei wird "nur" Run-Length-Encoded!}
  567. {     "fin" und "fout" werden per VAR-übergeben statt lokal definiert, um }
  568. {     damit *einiges* an Stack-Speicher zu sparen: beide dürfen nicht be- }
  569. {     nutzt sein, d.h. sie werden tatsächlich als lokaler Variablenersatz }
  570. {     verwendet!!!}
  571. VAR b:BYTE;
  572.     art:BYTE;
  573.     count:LONGINT;
  574.  
  575.  PROCEDURE dump;  {count mal art schreiben}
  576.  VAR i:LONGINT;
  577.      b:BYTE;
  578.  BEGIN
  579.   IF (art=ESC)
  580.    THEN BEGIN {Sonderfall ESC's}
  581.          FOR i:=1 TO (count SHR 8) DO
  582.           BEGIN
  583.            _Write(fout,ESC); _Write(fout,FFh); _Write(fout,ESC)
  584.           END;
  585.  
  586.           CASE (count AND $FF) OF
  587.            0:;
  588.            1:BEGIN
  589.               _Write(fout,ESC);
  590.               b:=0; _Write(fout,b);
  591.              END;
  592.            2:BEGIN
  593.               _Write(fout,ESC);
  594.               b:=1; _Write(fout,b);
  595.              END;
  596.        else BEGIN
  597.                  _Write(fout,ESC);
  598.                  b:=(count AND $FF)-1; _Write(fout,b);
  599.                  _Write(fout,ESC);
  600.                 END;
  601.           END; {of CASE}
  602.         END
  603.    ELSE BEGIN {normale Zeichen}
  604.          FOR i:=1 TO (count SHR 8) DO
  605.           BEGIN
  606.            _Write(fout,ESC); _Write(fout,FFh); _Write(fout,art)
  607.           END;
  608.  
  609.          IF (count AND $FF)<=3
  610.           THEN FOR i:=1 TO count AND $FF DO _Write(fout,art) {lohnt RLE nicht}
  611.       ELSE BEGIN {Rest RLE codieren}
  612.                 _Write(fout,ESC);
  613.                 b:=(count AND $FF)-1; _Write(fout,b);
  614.                 _Write(fout,art);
  615.                END;
  616.         END;
  617.  END;
  618.  
  619. VAR x,y:BYTE;
  620. BEGIN {of RLEcompress}
  621.  IF chatty THEN WRITELN('Starting RLE compression...');
  622.  _assign(fin,name1);  _reset(fin);
  623.  IF chatty THEN WRITELN('Size before    : ',_FileSize(fin):7);
  624.  IF _FileSize(fin)=0
  625.   THEN BEGIN
  626.         {$IFDEF test} WRITELN('*** Error: file has size 0 bytes!'); {$ENDIF}
  627.         _close(fin);
  628.         CompressError:=CompressErr_Size0;
  629.         exit
  630.        END;
  631.  _assign(fout,name2); _rewrite(fout);
  632.  
  633.  _ReadByte(fin,art); count:=1;
  634.  WHILE NOT _physicalEOF(fin) DO
  635.   BEGIN
  636.    _ReadByte(fin,b);
  637.    IF b=art
  638.     THEN inc(count)
  639.     ELSE BEGIN
  640.           dump; {count mal art schreiben, aber RLE-codiert!}
  641.           art:=b; count:=1;  {neues Zeichen übernehmen}
  642.          END;
  643.  
  644.    IF chatty AND (fin.position AND 1023=0)
  645.     THEN BEGIN
  646.           x:=wherex; y:=wherey;
  647.           write(fin.position:7);
  648.           gotoxy(x,y);
  649.          END;
  650.  
  651.   END;
  652.  dump;  {Rest rausschreiben}
  653.  
  654.  _Close(fin); _Close(fout);
  655.  
  656.  _assign(fout,name2); _reset(fout);
  657.  IF chatty THEN WRITELN('Size afterwards: ',_FileSize(fout):7);
  658.  _close(fout)
  659. END;
  660. {$ENDIF}
  661.  
  662. {$IFDEF RLE}
  663. PROCEDURE _Read(VAR f:FileOfByte; VAR b:BYTE);
  664. {rem: Diese Prozedur dient als "BlackBox" zum Benutzer: sie entspricht dem   }
  665. {     Aufruf Dos.Read(f,b), berücksichtigt aber RLE und Huffmankomprimierung!}
  666. BEGIN
  667.  IF NOT f.komprimiert
  668.   THEN BEGIN
  669.         ReadBits(f,b,8);
  670.         inc(f.position)
  671.        END
  672.   ELSE BEGIN {komprimierte Datei lesen}
  673.         IF f.RLEcount>0
  674.      THEN BEGIN {noch alte RLE-Daten}
  675.                b:=f.RLEchar; dec(f.RLEcount)
  676.               END
  677.      ELSE BEGIN {neues Datum aus Datei benötigt}
  678.                _ReadByte(f,b); {erhöht f.position um 1!}
  679.                IF b=ESC
  680.         THEN BEGIN {RLE-Datensequenz kommt!}
  681.                       _ReadByte(f,b);      {erhöht f.position um 1!}
  682.                       dec(f.position); {gleich rückgängig machen!}
  683.                       CASE b OF
  684.                        0: b:=ESC;  {1x ESC, gleich zurückgeben, kein RLE}
  685.                1: BEGIN {2x ESC, 1 zurückgeben, 1 behalten}
  686.                            b:=ESC;
  687.                            f.RLEcount:=1;
  688.                            f.RLEchar :=ESC
  689.                           END;
  690.                else BEGIN {andere Sequenz, 3.Byte benötigt}
  691.                              f.RLEcount:=b;
  692.                              _ReadByte(f,b);  {auch gleich zurückgeben}
  693.                              dec(f.position); {f.position korrigieren}
  694.                              f.RLEchar :=b
  695.                             END;
  696.                       END; {of CASE}
  697.                      END;
  698.               END;
  699.        END;
  700. END;
  701. {$ELSE}
  702. PROCEDURE _Read(VAR f:FileOfByte; VAR b:BYTE);
  703. BEGIN
  704.  _ReadByte(f,b)
  705. END;
  706. {$ENDIF}
  707.  
  708. PROCEDURE _BlockRead(VAR f:FileOfByte; var buf; count: Word);
  709. {rem: Liest count Bytes aus der Datei f an die Stelle, auf die buf zeigt.}
  710. {     Funktioniert analog zu Dos.BlockRead(f,buf,count) mit dem Unter-   }
  711. {     schied, daß Huffman- und RLE-Codierung berücksichtigt werden.      }
  712. VAR s,o,i:WORD;
  713.     b:BYTE;
  714. BEGIN
  715.  s:=SEG(buf) +(OFS(buf) SHR 4);
  716.  o:=OFS(buf) AND $F;
  717.  
  718.  FOR i:=0 TO count-1 DO
  719.   BEGIN
  720.    _Read(f,b);
  721.    MEM[s:o]:=b;
  722.    inc(o);
  723.    IF o=65520 THEN BEGIN inc(s,65520 DIV 16); o:=0 END;  {Überlauf vermeiden}
  724.   END;
  725. END;
  726.  
  727. PROCEDURE __BlockRead(VAR f:FileOfByte; var buf; count: Word; VAR result:WORD);
  728. {rem: Liest count Bytes aus der Datei f an die Stelle, auf die buf zeigt  }
  729. {     und gibt in result zurück, wieviele Bytes tatsächlich gelesen wurden}
  730. {     Funktioniert analog zu Dos.BlockRead(f,buf,count,result) mit dem    }
  731. {     Unterschied, daß Huffman- und RLE-Codierung berücksichtigt werden.  }
  732. VAR s,o,i:WORD;
  733.     b:BYTE;
  734. BEGIN
  735.  s:=SEG(buf) +(OFS(buf) SHR 4);
  736.  o:=OFS(buf) AND $F;
  737.  
  738.  result:=f.lenunkom-f.position; {max. #Bytes, die gelesen werden können}
  739.  IF count>result
  740.   THEN count :=result
  741.   ELSE result:=count;
  742.  FOR i:=0 TO count-1 DO
  743.   BEGIN
  744.    _Read(f,b);
  745.    MEM[s:o]:=b;
  746.    inc(o);
  747.    IF o=65520 THEN BEGIN inc(s,65520 DIV 16); o:=0 END;  {Überlauf vermeiden}
  748.   END;
  749. END;
  750.  
  751. PROCEDURE _BlockWrite(VAR f:FileOfByte; var buf; count: Word);
  752. {rem: Schreibt count Bytes von der Stelle, auf die buf zeigt, nach f.    }
  753. {     Funktioniert analog zu Dos.BlockWrite(f,buf,count) mit dem Unter-  }
  754. {     schied, daß prinzipiell geblockt wird!                             }
  755. VAR s,o,i:WORD;
  756. BEGIN
  757.  s:=SEG(buf) +(OFS(buf) SHR 4);
  758.  o:=OFS(buf) AND $F;
  759.  FOR i:=0 TO count-1 DO
  760.   BEGIN
  761.    _Write(f,MEM[s:o]);
  762.    inc(o);
  763.    IF o=65520 THEN BEGIN inc(s,65520 DIV 16); o:=0 END;  {Überlauf vermeiden}
  764.   END;
  765. END;
  766.  
  767. PROCEDURE __BlockWrite(VAR f:FileOfByte; var buf; count: Word; VAR result:WORD);
  768. {rem: Schreibt count Bytes von der Stelle, auf die buf zeigt, nach f und }
  769. {     gibt in result zurück, wieviel Bytes tatsächlich geschrieben wurden}
  770. {     Funktioniert analog zu Dos.BlockWrite(f,buf,count,result) mit dem  }
  771. {     Unterschied, daß prinzipiell geblockt wird!                        }
  772. VAR s,o,i:WORD;
  773. BEGIN
  774.  s:=SEG(buf) +(OFS(buf) SHR 4);
  775.  o:=OFS(buf) AND $F;
  776.  
  777.  FOR i:=0 TO count-1 DO
  778.   BEGIN
  779.    {$I-}
  780.    _Write(f,MEM[s:o]);
  781.    IF IOresult<>0 THEN BEGIN result:=i; exit END;
  782.    inc(o);
  783.    IF o=65520 THEN BEGIN inc(s,65520 DIV 16); o:=0 END;  {Überlauf vermeiden}
  784.   END;
  785.  result:=count;
  786. END;
  787.  
  788. PROCEDURE compress(name1,name2:PathStr; chatty:BOOLEAN);
  789. { in: name1 = Name der zu komprimierenden Datei}
  790. {     name2 = Name der zu erzeugenden Datei}
  791. {     chatty= TRUE für: Programm ist etwas redseliger während der Arbeit}
  792. {out: name2 = Dateiinhalt von name1, aber komprimiert}
  793. {     CompressError = CompressErr_AlreadyCompressed, wenn Datei bereits   }
  794. {     komprimiert ist, oder CompressErr_Size0, falls Datei leer ist, oder }
  795. {     einer der von anderen Routinen durchgereichten Fehler}
  796. {rem: Komprimiert die Datei name1 und legt das Ergebnis in Datei name2 ab.}
  797. {     Die Datei wird Huffman (und RLE) codiert.}
  798. {     Zur RLE-Komprimierung wird eine temporäre Datei unter dem Namen, der}
  799. {     in "TemporaryName" steht angelegt -im aktuellen Verzeichnis bzw. im }
  800. {     Verzeichnis das durch die Environmentvariable "TEMP" oder "TMP" an- }
  801. {     gegeben ist.}
  802. VAR anzahl:ARRAY[0..255] OF LONGINT;
  803.     wert,i,j:BYTE;
  804.     m:INTEGER;
  805.     ungleich0,ReallyWritten:WORD;
  806.     von,nach:FileOfByte;
  807.     cast:RECORD {Header: Längenbytes (un)komprimiert}
  808.           unkom,kom:LONGINT;
  809.          END;
  810.     start:code;
  811.     orgLen,k:LONGINT;
  812. {$IFDEF RLE}
  813.     tempName:PathStr;
  814. {$ENDIF}
  815.      
  816.  
  817.    PROCEDURE BuildTree;
  818.    {rem: Erzeugt gemäß den in "anzahl[]" stehenden Auftrittshäufigkeiten der }
  819.    {     Zeichen den Huffmanbaum und schreibt dessen Wurzel nach "nach.stamm"}
  820.    VAR help:branch;
  821.        i,min1,min2,gzeichen:BYTE;
  822.        tree:ARRAY[0..255] OF Pbranch;
  823.  
  824.      PROCEDURE findmin(VAR m1,m2:BYTE);
  825.      {rem: Findet die 2 Zeichen mit der kleinsten (und zweitkleinsten) Auf-  }
  826.      {     trittswahrscheinlichkeit}
  827.      VAR anz1,anz2:LONGINT;
  828.          i:BYTE;
  829.      BEGIN
  830.       anz1:=MaxLongint;
  831.       anz2:=MaxLongint;
  832.       FOR i:=0 TO 255 DO
  833.        IF tree[i]<>NIL
  834.         THEN BEGIN
  835.               IF anz1>=anzahl[i]
  836.            THEN BEGIN
  837.                      anz2:=anz1; anz1:=anzahl[i]; m2:=m1; m1:=i
  838.                     END
  839.            ELSE BEGIN
  840.                      IF anz2>=anzahl[i] THEN BEGIN anz2:=anzahl[i]; m2:=i END
  841.                     END;
  842.              END;
  843.      END;
  844.  
  845.    BEGIN {of BuildTree}
  846.     gzeichen:=255;
  847.     FOR i:=0 TO 255 DO
  848.      IF anzahl[i]=0
  849.       THEN BEGIN
  850.             tree[i]:=NIL; dec(gzeichen)
  851.            END
  852.       ELSE BEGIN
  853.             new(tree[i]);
  854.             tree[i]^.zeichen:=i;
  855.             tree[i]^.links:=NIL;
  856.             tree[i]^.rechts:=NIL
  857.            END;
  858.     FOR i:=1 TO gzeichen DO
  859.      BEGIN
  860.       findmin(min1,min2);
  861.       help.zeichen:=min1;
  862.       help.links:=tree[min1];
  863.       help.rechts:=tree[min2];
  864.       new(tree[min1]);
  865.       tree[min1]^:=help;
  866.       anzahl[min1]:=anzahl[min1]+anzahl[min2];
  867.       tree[min2]:=NIL
  868.      END;
  869.     i:=0;
  870.     WHILE tree[i]=NIL DO INC(i);
  871.     nach.stamm:=tree[i]^;
  872.    END;
  873.  
  874.    PROCEDURE BuildLookupTable(gabel:branch; startwert:code);
  875.    {rem: Erzeugt aus dem Huffmancodebaum eine Lookup-Tabelle für eine      }
  876.    {     schnellere Codierung; Ergebnis steht danach in nach.codes[]:      }
  877.    {     nach.codes[i].bitcount enthält die Länge (in Bits) von Zeichen i, }
  878.    {     nach.codes[i].itself[] enthält den Bitcode von Zeichen i (gepackt)}
  879.    BEGIN
  880.     IF (gabel.links=NIL) AND (gabel.rechts=NIL)
  881.      THEN nach.codes[gabel.zeichen]:=startwert
  882.      ELSE BEGIN
  883.            startwert.itself[startwert.bitcount SHR 3]:=startwert.itself[startwert.bitcount SHR 3]
  884.             AND ANDMask[startwert.bitcount AND 7];  {Bit löschen}
  885.            inc(startwert.bitcount);
  886.            BuildLookupTable(gabel.links^,startwert);
  887.            dispose(gabel.links);
  888.            dec(startwert.bitcount);
  889.  
  890.            startwert.itself[startwert.bitcount SHR 3]:=startwert.itself[startwert.bitcount SHR 3]
  891.             OR ORMask[startwert.bitcount AND 7]; {Bit setzen}
  892.            inc(startwert.bitcount);
  893.            BuildLookupTable(gabel.rechts^,startwert);
  894.            dispose(gabel.rechts);
  895.           END;
  896.    END;
  897.  
  898. VAR oldx,oldy:BYTE;
  899. BEGIN {of compress}
  900.  _assign(von,name1);
  901.  _reset(von);
  902.  IF CompressError<>CompressErr_NoError THEN exit;
  903.  orgLen:=_FileSize(von);
  904.  _close(von);
  905.  IF CompressError<>CompressErr_NoError THEN exit;
  906.  
  907.  IF von.komprimiert
  908.   THEN BEGIN
  909.         {$IFDEF test} WRITELN('*** Error: file already kompressed!'); {$ENDIF}
  910.         CompressError:=CompressErr_AlreadyCompressed;
  911.         exit
  912.        END;
  913.  IF orgLen=0
  914.   THEN BEGIN
  915.         {$IFDEF test} WRITELN('*** Error: file has length 0!'); {$ENDIF}
  916.         CompressError:=CompressErr_Size0;
  917.         exit
  918.        END;
  919.  
  920.  {$IFDEF RLE}
  921.  tempName:=GetEnv('TEMP');
  922.  IF tempName='' THEN tempName:=GetEnv('TMP');
  923.  IF Length(tempName)>0
  924.   THEN IF tempName[Length(tempName)]<>'\' THEN tempName:=tempName+'\';
  925.  tempName:=tempName+TemporaryFile;
  926.  RLEcompress(name1,tempName,von,nach,chatty); {"von","nach" sind beide frei!}
  927.  IF CompressError<>CompressErr_NoError
  928.   THEN BEGIN {hier wird die temporäre Datei gelöscht, die noch in "nach" steht}
  929.         {$IFDEF RLE} close(nach.datei); erase(nach.datei); {$ENDIF}
  930.         exit;
  931.        END;
  932.  {$IFDEF test} WRITELN('RLE-Codierung durchgeführt...'); {$ENDIF}
  933.  name1:=tempName;
  934.  {$ENDIF}
  935.  
  936.  IF chatty
  937.   THEN BEGIN
  938.         WRITELN('Starting Huffman compression...');
  939.         WRITELN('Size before    : ',nach.lenunkom:7);
  940.        END;
  941.  
  942.  _assign(von,name1);
  943.  _reset(von);
  944.  IF CompressError<>CompressErr_NoError
  945.   THEN BEGIN
  946.         _close(von);
  947.         exit;
  948.        END;
  949.  FillChar(anzahl,SizeOf(anzahl),0);
  950.  FOR k:=1 TO _FileSize(von) DO
  951.   BEGIN
  952.    _Read(von,wert);
  953.    inc(anzahl[wert])
  954.   END;
  955.  _close(von);
  956.  
  957.  _assign(von,name1);  _reset(von);
  958.  IF CompressError<>CompressErr_NoError
  959.   THEN BEGIN
  960.         _close(von);
  961.         exit;
  962.        END;
  963.  _assign(nach,name2); _rewrite(nach);
  964.  IF CompressError<>CompressErr_NoError
  965.   THEN BEGIN
  966.         _close(von); _close(nach);
  967.         exit;
  968.        END;
  969.  
  970.  {Wenn die Datei aus nur einem Zeichen besteht (z.B.: "aaaaa"), dann wäre}
  971.  {der Huffman-Baum "degeneriert", er hätte die Höhe 0 und seine Codes    }
  972.  {entsprechend die Länge 0! Um dies zu verhindern wird in diesem Fall    }
  973.  {einfach das Auftreten eines willkürlichen zweiten Zeichens simuliert.  }
  974.  ungleich0:=0;
  975.  FOR i:=0 TO 255 DO IF anzahl[i]<>0 THEN inc(ungleich0);
  976.  IF ungleich0=1  {besteht gesamte Datei aus demselben Zeichen?}
  977.   THEN BEGIN {ja, künstlich das Auftreten eines 2.Zeichens simulieren}
  978.         IF anzahl[0]=0 THEN inc(anzahl[0])
  979.         ELSE inc(anzahl[1])
  980.        END;
  981.  
  982.  {$IFDEF test} WRITELN('Häufigkeiten ermittelt...'); {$ENDIF}
  983.  BuildTree;
  984.  {$IFDEF test} WRITELN('Codes generiert...'); {$ENDIF}
  985.  FillChar(start,SizeOf(start),0);
  986.  FOR j:=0 TO 255 DO nach.codes[j]:=start;
  987.  BuildLookupTable(nach.stamm,start);
  988.  {$IFDEF test} WRITELN('Lookuptable generiert...'); {$ENDIF}
  989.  
  990.  nach.lenunkom:=orgLen;  {unkomprimierte Länge übernehmen}
  991.  FOR i:=1 TO SizeOf(Header) DO
  992.   WriteBits(nach,Kennung[i],8);
  993.  FOR i:=1 TO SizeOf(LONGINT)+SizeOf(LONGINT) DO
  994.   WriteBits(nach,0,8); {Platz lassen für Längeninfos}
  995.  j:=255; WHILE anzahl[j]=0 DO dec(j);
  996.  FOR i:=0 TO j DO
  997.   BEGIN
  998.    FOR m:=0 TO nach.codes[i].bitcount-1 DO
  999.     WriteBits(nach,(nach.codes[i].itself[m SHR 3] SHR (m AND 7)) AND 1,2);
  1000.    WriteBits(nach,2,2)  {2=EndOfOneCode}
  1001.   END;
  1002.  IF j<>255
  1003.   THEN WriteBits(nach,3,2); {3=EndOfAllCodes: Abkürzen, falls möglich}
  1004.  
  1005.  WHILE NOT _physicalEOF(von) DO
  1006.   BEGIN
  1007.    _ReadByte(von,wert);
  1008.    FOR i:=0 TO nach.codes[wert].bitcount-1 DO
  1009.     WriteBits(nach,(nach.codes[wert].itself[i SHR 3] SHR (i AND 7)) AND 1,1);
  1010.    inc(nach.position);
  1011.  
  1012.    IF chatty AND (nach.position AND 1023=0)
  1013.     THEN BEGIN
  1014.           oldx:=wherex; oldy:=wherey;
  1015.           write(von.position:7,' -> ',nach.lenbitskom SHR 3:7);
  1016.           gotoxy(oldx,oldy);
  1017.          END;
  1018.   END;
  1019.  
  1020.  IF CompressError<>CompressErr_NoError
  1021.   THEN BEGIN {wahrscheinlich ist die Platte voll?}
  1022.         _Close(von); Close(nach.datei);
  1023.         exit;
  1024.        END;
  1025.  
  1026.  {Jetzt ein "Flush" nachbilden, aber exakte Größe merken}
  1027.  cast.kom :=nach.lenbitskom;
  1028.  IF nach.bitzaehl<>8
  1029.   THEN BEGIN
  1030.         WriteBits(nach,0,nach.bitzaehl); {letztes Byte auffüllen}
  1031.         inc(nach.position)
  1032.        END;
  1033.  {$I-}
  1034.  BlockWrite(nach.datei,nach.buf,nach.bufIndex,ReallyWritten);
  1035.  {$IFDEF IOcheck} {$I+} {$ENDIF}
  1036.  IF IOresult=103
  1037.   THEN BEGIN
  1038.         CompressError:=CompressErr_FileNotOpen;
  1039.         nach.bufIndex:=0; nach.bitzaehl:=8; {nur der Optik halber...}
  1040.         exit
  1041.        END
  1042.  ELSE IF ReallyWritten<>nach.bufIndex
  1043.   THEN BEGIN
  1044.         CompressError:=CompressErr_DiskFull;
  1045.         nach.bufIndex:=0; nach.bitzaehl:=8; {nur der Optik halber...}
  1046.         exit
  1047.        END
  1048.  ELSE IF IOresult<>0
  1049.   THEN BEGIN
  1050.         CompressError:=CompressErr_Unknown;
  1051.         nach.bufIndex:=0; nach.bitzaehl:=8; {nur der Optik halber...}
  1052.         exit
  1053.        END;
  1054.  
  1055.  Seek(nach.datei,SizeOf(Header)); {jetzt Längenbytes eintragen}
  1056.  cast.unkom:=nach.lenunkom;
  1057.  BlockWrite(nach.datei,cast,SizeOf(cast));
  1058.  
  1059.  _Close(von);
  1060.  IF chatty THEN WRITELN('Size afterwards: ',nach.lenbitskom SHR 3:7);
  1061.  Close(nach.datei); {kein _Close(), um erneutes Flush zu vermeiden!}
  1062.  {$IFDEF RLE} erase(von.datei); {$ENDIF}
  1063. END;
  1064.  
  1065. PROCEDURE decompress(name1,name2:PathStr; chatty:BOOLEAN);
  1066. { in: name1 = Name des zu dekomprimierenden Files}
  1067. {     name2 = Name für dekomprimiertes ergebnis  }
  1068. {     chatty= TRUE für: Programm ist etwas redseliger während der Arbeit}
  1069. {out: Datei name2 wurde erzeugt}
  1070. {rem: Dekomprimiert die Datei name1 und schreibt das Ergebnis nach name2.}
  1071. {     Enthält die Datei mehr als ein logisches File, so werden alle (bis }
  1072. {     zum physikalischen Ende der Datei) dekomprimiert; dies setzt aber  }
  1073. {     voraus, daß alle logischen Dateien in der physikalischen Datei kom-}
  1074. {     primiert sind, denn sonst kann die Prozedur ja nicht feststellen,  }
  1075. {     wann eine logische Datei zuende ist.}
  1076. {     name1 und name2 müssen zwei verschiedene Dateien bezeichnen!}
  1077. LABEL break;
  1078. VAR wert:BYTE;
  1079.     von,nach:FileOfByte;
  1080.     oldx,oldy:BYTE;
  1081. BEGIN
  1082.  IF chatty THEN WRITELN('Starting decompression...');
  1083.  _assign(von,name1);  _reset(von);
  1084.  IF CompressError<>CompressErr_NoError
  1085.   THEN BEGIN
  1086.         _close(von); {trotzdem versuchen, die Datei zu schließen}
  1087.         exit
  1088.        END;
  1089.  _assign(nach,name2); _rewrite(nach);
  1090.  IF CompressError<>CompressErr_NoError
  1091.   THEN BEGIN
  1092.         _close(von); {trotzdem versuchen, die Datei zu schließen}
  1093.         exit
  1094.        END;
  1095.  
  1096.  WHILE NOT _physicalEOF(von) DO    {Ist als Ersatz zu verstehen für:}
  1097.   BEGIN                            {while not _eof(von) do          }
  1098.    WHILE NOT _logicalEOF(von) DO   { begin                          }
  1099.     BEGIN                          {  _Read(von,wert);              }
  1100.      _Read(von,wert);              {  _write(nach,wert)             }
  1101.      _Write(nach,wert);            { end;                           }
  1102.      IF chatty AND (von.position AND 1023=0)
  1103.       THEN BEGIN
  1104.             oldx:=wherex; oldy:=wherey;
  1105.             write(von.position:7,' -> ',nach.position:7);
  1106.             gotoxy(oldx,oldy);
  1107.            END;
  1108.     END;                           {Linksstehende Sequenz kommt auch}
  1109.    IF not _physicalEOF(von)        {mit mehreren Dateien in einem   }
  1110.     THEN Resync(von)               {File zurecht!}
  1111.   END;
  1112.  _Close(von); _Close(nach);
  1113.  IF chatty THEN ClrEol;
  1114. END;
  1115.  
  1116. {$IFDEF test}
  1117. BEGIN
  1118.  IF ParamCount=3 THEN BEGIN s:=ParamStr(3); ch:=UpCase(s[1]) END;
  1119.  IF (ParamCount<>3) OR (NOT (ch IN ['C','K','D']))
  1120.   THEN BEGIN
  1121.         WRITELN('***Error! Syntax: ',ParamStr(0)+' oldFile newFile {d|k}');
  1122.         Halt
  1123.        END;
  1124.  
  1125.  datei1:=ParamStr(1);
  1126.  datei2:=ParamStr(2);
  1127.  IF (upcase(ch)='K') OR (upcase(ch)='C')
  1128.   THEN BEGIN
  1129.         compress(datei1,datei2,TRUE);
  1130.         WRITELN('Code auf Disk geschrieben...');
  1131.  
  1132.         _assign(temp,datei2); _reset(temp);
  1133.         vorher:=_FileSize(temp);
  1134.         nachher:=temp.lenbitskom;
  1135.         gesamt:=FileSize(temp.datei);
  1136.         _close(temp);
  1137.  
  1138.         WRITELN('vorher: ',vorher:5,'[Bytes];  nachher: ',nachher:5,
  1139.                 '[Bits];  nachher+Header: ',gesamt:5,'[Bytes]');
  1140.        END
  1141.   ELSE BEGIN
  1142.         decompress(datei1,datei2,TRUE);
  1143.         WRITELN('Dekompression durchgeführt...');
  1144.        END;
  1145. {$ENDIF}
  1146. END.
  1147.